
package com.yadong.smarthome2.connections;

import java.util.Collection;
import java.util.LinkedHashMap;

import com.yadong.smarthome2.connections.Connection.ConnectionListener;
import com.yadong.smarthome2.connections.Connection.ConnectionState;
import com.yadong.smarthome2.device.Device;
import com.yadong.smarthome2.device.Device.DeviceState;
import com.yadong.smarthome2.device.DeviceManagerService;
import com.yadong.smarthome2.messageparser.RequestManager;
import com.yadong.smarthome2.messageparser.RequestManager.RequestTimeoutListener;
import com.yadong.smarthome2.protocol2.ALog;
import com.yadong.smarthome2.protocol2.RequestProtocol.Request;

public class ConnectionManager implements ConnectionListener,
        RequestTimeoutListener {
    private static final int MAX_CONNECTION_SIZE = 4;
    private static ConnectionManager sInstance;
    private MessageSendThread mMessageSendThread;
    private RequestManager mRequestManager;
    private DeviceManagerService mDeviceManagerService;
    private ResultListener mResultListener;
    private LinkedHashMap<Device, Connection> mConnections =
            new LinkedHashMap<Device, Connection>();
    private ConnectionGarbageCollection mConnectionGarbageCollection;

    private ConnectionManager(DeviceManagerService
            deviceManagerService) {
        mMessageSendThread = new MessageSendThread();
        mRequestManager = new RequestManager();
        mConnectionGarbageCollection = new ConnectionGarbageCollection(this);
        mRequestManager.setRequestTimeoutListener(this);
        mDeviceManagerService = deviceManagerService;
        mResultListener = deviceManagerService;
        mMessageSendThread.start();
        mRequestManager.start();
    }

    public static ConnectionManager getInstance(DeviceManagerService
            deviceManagerService) {
        if (sInstance == null) {
            sInstance = new ConnectionManager(deviceManagerService);
        }
        return sInstance;
    }

    /**
     * create connection for device. and connect it.
     * @param device
     */
    public void connect(Device device) {
        synchronized (mConnections) {
            while (mConnections.size() >= MAX_CONNECTION_SIZE) {
                for (Device key : mConnections.keySet()) {
                    Connection connection = mConnections.get(key);
                    if (connection.getConnectionState() != ConnectionState.WORKING) {
                        connection.stopRunning();
                    }
                }
            }
            Connection connection = mConnections.get(device);
            if (connection == null
                    || connection.getConnectionState() == ConnectionState.DISCONNECTED) {
                connection = ConnectionFactory.createConnection(device,
                        this, mMessageSendThread, mRequestManager, mResultListener);
                connection.start();
            }
        }
    }

    /**
     * force disconnect the device
     * 
     * @param device
     */
    public void forceDisConnect(Device device) {
        synchronized (mConnections) {
            Connection connection = mConnections.remove(device);
            if (connection != null) {
                connection.forceStop();
            }
        }
    }

    /**
     * get device connection
     * @param device
     * @return
     */
    public Connection getConnection(Device device) {
        synchronized (mConnections) {
            return mConnections.get(device);
        }
    }

    /**
     * get all connected  devices
     * @return
     */
    public Collection<Connection> getConnections() {
        synchronized (mConnections) {
            return mConnections.values();
        }
    }

    public void forceStopAll() {
        synchronized (mConnections) {
            for (Device device: mConnections.keySet()) {
                mConnections.get(device).forceStop();
            }
        }
        mConnectionGarbageCollection.forceStop();
    }

    public void onConnectionDisconnected(Connection connection) {
        synchronized (mConnections) {
            mConnections.remove(connection);
            connection.getDevice().setState(DeviceState.PAIRED);
            mDeviceManagerService.onConnectionDisconnected(connection);
        }
    }

    @Override
    public void onConnectionFaild(Connection connection) {
        mDeviceManagerService.onConnectionFaild(connection);
    }

    public void onConnectionConnected(Connection connection) {
        synchronized (mConnections) {
            mConnections.put(connection.getDevice(), connection);
            connection.getDevice().setState(DeviceState.CONNECTED);
            mDeviceManagerService.onConnectionConnected(connection);
        }
    }

    @Override
    public void onDeviceInitialized(Connection connection) {
        connection.getDevice().setState(DeviceState.FUNCIONLOADED);
        mDeviceManagerService.onDeviceInitialized(connection);
    }

    @Override
    public void onRequestTimeOut(Device device, Request request) {
        ALog.loge(ConnectionManager.this, "on request time out for device:"
                + device.getDeviceDescription() + "  disconnect it");
        // TODO if request do not response disconnect the device
        // mDeviceManagerService.disconnectDevice(device);
    }
}
